#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#include "Platform.h"

#include "Scintilla.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "CellBuffer.h"

LineNumberModel::LineNumberModel() :
	starts(256) {
	ClearLines();
}

LineNumberModel::~LineNumberModel() {
	starts.Clear();
}

void LineNumberModel::ClearLines() {
	starts.Clear();
}

void LineNumberModel::AddListener(LineNumberModelEvent *lineNumberListener) {
	listeners.push_back(lineNumberListener);
}

void LineNumberModel::InsertText(int line, int delta) {
	starts.ModifyDelta(line, delta);
}

void LineNumberModel::InsertLine(int line, int position, bool lineStart) {
	starts.Insert(line, position);
	if ((line > 0) && lineStart)
		line--;

	vector<LineNumberModelEvent*>::iterator it = listeners.begin();
	while (it != listeners.end()) {
		LineNumberModelEvent* listener = *it;
		listener->OnInsertLine(line);
	}
}

void LineNumberModel::SetLineStart(int line, int position) {
	starts.SetValue(line, position);
}

void LineNumberModel::RemoveLine(int line) {
	starts.Remove(line);
	vector<LineNumberModelEvent*>::iterator it = listeners.begin();
	while (it != listeners.end()) {
		LineNumberModelEvent* listener = *it;
		listener->OnRemoveLine(line);
	}
}

int LineNumberModel::LineFromPosition(int pos) const {
	return starts.IndexOf(pos);
}

Action::~Action() {
}

ActionInsert::ActionInsert(TextModel* model_, int position_, const char* data_, int len_) {
	model = model_;
	data.reset(new string(data_, len_));
}
void ActionInsert::Do() {
	model->_InsertString(position, data->c_str(), data->length());
}
void ActionInsert::Undo() {
	model->_DeleteChars(position, data->length());
}
ActionRemove::ActionRemove(TextModel* model_, int position_, int len_) {
	model = model_;
	char* data_ = new char[len_ + 1];
	memset(data_, 0, len_ + 1);
	model->GetCharRange(data_, position, len_);
	data.reset(new string(data_, len_));
}
void ActionRemove::Do() {
	model->_DeleteChars(position, data->length());
}
void ActionRemove::Undo() {
	model->_InsertString(position, data->c_str(), data->length());
}

UndoHistory::~UndoHistory() {
}

TextModel::~TextModel() {
}

bool TextModel::SetStyleFor(int position, int lengthStyle, char styleValue, char mask) {
	bool changed = false;
	PLATFORM_ASSERT(lengthStyle == 0 ||
			(lengthStyle > 0 && lengthStyle + position <= style.Length()));
	while (lengthStyle--) {
		char curVal = style.ValueAt(position);
		if ((curVal & mask) != styleValue) {
			style.SetValueAt(position, static_cast<char> ((curVal & ~mask) | styleValue));
			changed = true;
		}
		position++;
	}
	return changed;
}

void TextModel::_InsertString(int position, const char *s, int insertLength) {
	if (insertLength == 0)
		return;
	assert(insertLength > 0);

	text.InsertFromArray(position, s, 0, insertLength);
	style.InsertValue(position, insertLength, 0);

	int lineInsert = lineNumbers.LineFromPosition(position) + 1;
	bool atLineStart = lineNumbers.LineStart(lineInsert - 1) == position;
	// Point all the lines after the insertion point further along in the buffer
	lineNumbers.InsertText(lineInsert - 1, insertLength);
	char chPrev = text.ValueAt(position - 1);
	char chAfter = text.ValueAt(position + insertLength);
	if (chPrev == '\r' && chAfter == '\n') {
		// Splitting up a crlf pair actionType position
		lineNumbers.InsertLine(lineInsert, position, false);
		lineInsert++;
	}
	char ch = ' ';
	for (int i = 0; i < insertLength; i++) {
		ch = s[i];
		if (ch == '\r') {
			lineNumbers.InsertLine(lineInsert, (position + i) + 1, atLineStart);
			lineInsert++;
		} else if (ch == '\n') {
			if (chPrev == '\r') {
				// Patch up what was end of line
				lineNumbers.SetLineStart(lineInsert - 1, (position + i) + 1);
			} else {
				lineNumbers.InsertLine(lineInsert, (position + i) + 1, atLineStart);
				lineInsert++;
			}
		}
		chPrev = ch;
	}
	// Joining two lines where last insertion is cr and following substance starts with lf
	if (chAfter == '\n') {
		if (ch == '\r') {
			// End of line already in buffer so drop the newly created one
			lineNumbers.RemoveLine(lineInsert - 1);
		}
	}
}

void TextModel::_DeleteChars(int position, int deleteLength) {
	if (deleteLength == 0)
		return;

	if ((position == 0) && (deleteLength == text.Length())) {
		lineNumbers.ClearLines();
	} else {
		// Have to fix up line positions before doing deletion as looking actionType text in buffer
		// to work out which lines have been removed

		int lineRemove = lineNumbers.LineFromPosition(position) + 1;
		lineNumbers.InsertText(lineRemove - 1, -(deleteLength));
		char chPrev = text.ValueAt(position - 1);
		char chBefore = chPrev;
		char chNext = text.ValueAt(position);
		bool ignoreNL = false;
		if (chPrev == '\r' && chNext == '\n') {
			// Move back one
			lineNumbers.SetLineStart(lineRemove, position);
			lineRemove++;
			ignoreNL = true; // First \n is not real deletion
		}

		char ch = chNext;
		for (int i = 0; i < deleteLength; i++) {
			chNext = text.ValueAt(position + i + 1);
			if (ch == '\r') {
				if (chNext != '\n') {
					lineNumbers.RemoveLine(lineRemove);
				}
			} else if (ch == '\n') {
				if (ignoreNL) {
					ignoreNL = false; // Further \n are real deletions
				} else {
					lineNumbers.RemoveLine(lineRemove);
				}
			}

			ch = chNext;
		}
		// May have to fix up end if last deletion causes cr to be next to lf
		// or removes one of a crlf pair
		char chAfter = text.ValueAt(position + deleteLength);
		if (chBefore == '\r' && chAfter == '\n') {
			// Using lineRemove-1 as cr ended line before start of deletion
			lineNumbers.RemoveLine(lineRemove - 1);
			lineNumbers.SetLineStart(lineRemove - 1, position + 1);
		}
	}
	text.DeleteRange(position, deleteLength);
	style.DeleteRange(position, deleteLength);
}
